package com.atguigu.process.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.atguigu.auth.service.SysUserService;
import com.atguigu.model.process.Process;
import com.atguigu.model.process.ProcessRecord;
import com.atguigu.model.process.ProcessTemplate;
import com.atguigu.model.system.SysUser;
import com.atguigu.process.mapper.OaProcessMapper;
import com.atguigu.process.service.OaProcessRecordService;
import com.atguigu.process.service.OaProcessService;
import com.atguigu.process.service.OaProcessTemplateService;
import com.atguigu.security.config.LoginUserInfoHelper;
import com.atguigu.vo.process.ApprovalVo;
import com.atguigu.vo.process.ProcessFormVo;
import com.atguigu.vo.process.ProcessQueryVo;
import com.atguigu.vo.process.ProcessVo;
import com.atguigu.wechat.service.MessageService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.DateTimeLiteralExpression;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.EndEvent;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.history.HistoricTaskInstanceQuery;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.task.TaskQuery;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.*;
import java.util.zip.ZipInputStream;

/**
 * <p>
 * 审批类型 服务实现类
 * </p>
 *
 * @author atguigu
 * @since 2025-06-27
 */
@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class OaProcessServiceImpl extends ServiceImpl<OaProcessMapper, Process> implements OaProcessService {
    @Resource
    private OaProcessMapper oaProcessMapper;

    @Resource
    private RepositoryService repositoryService;

    @Autowired
    private OaProcessTemplateService oaProcessTemplateService;

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private TaskService taskService;

    @Autowired
    private OaProcessRecordService oaProcessRecordService;
    
    @Autowired
    private HistoryService historyService;

    @Autowired
    private MessageService messageService;


    /**
     * 分页查询
     * @param pageParam
     * @param processQueryVo
     * @return
     */
    @Override
    public IPage<ProcessVo> selectPage(Page<ProcessVo> pageParam, ProcessQueryVo processQueryVo) {
        IPage<ProcessVo> page = oaProcessMapper.selectPage(pageParam, processQueryVo);
        return page;
    }

    // deployPath = "processes/" + fileName

    /**
     * 发布流程
     * @param deployPath
     */
    @Override
    public void deployByZip(String deployPath) {
        // 定义zip输入流 获取类路径下的文件zip
        //返回一个 InputStream，用于读取文件内容
        InputStream inputStream = this
                .getClass()
                .getClassLoader()
                .getResourceAsStream(deployPath);
        //将普通的 InputStream 包装成 ZipInputStream。
        ZipInputStream zipInputStream = new ZipInputStream(inputStream);

        // 流程部署
        Deployment deployment = repositoryService.createDeployment()
                .addZipInputStream(zipInputStream)
                //TODO不填name，流程部署名称将为空 ，这里用当前时间为流程部署名称， 流程部署文件中有名称，但是我不会获取
                .name(LocalDateTime.now().toString())
                .deploy();
        System.out.println("流程部署id：" + deployment.getId());
        System.out.println("流程部署名称：" + deployment.getName());
    }

    /**
     * 启动流程实例
     * @param processFormVo
     */
    public void startUp(ProcessFormVo processFormVo) {
        //获取当前用户
        SysUser sysUser = sysUserService.getById(LoginUserInfoHelper.getUserId());
        //获取流程模板
        ProcessTemplate processTemplate = oaProcessTemplateService.getById(processFormVo.getProcessTemplateId());
        //保存提交审批信息到业务表，oa_process
        Process process = new Process();
        BeanUtils.copyProperties(processFormVo, process);
        String workNo = System.currentTimeMillis() + "";
        process.setProcessCode(workNo);
        process.setUserId(LoginUserInfoHelper.getUserId());
        process.setFormValues(processFormVo.getFormValues());
        process.setTitle(sysUser.getName() + "发起" + processTemplate.getName() + "申请");
        process.setStatus(1);
        oaProcessMapper.insert(process);

        //绑定业务id
        String businessKey = String.valueOf(process.getId());
        //流程参数
        Map<String, Object> variables = new HashMap<>();
        //将表单数据放入流程实例(流程参数)中
        //将对象转成JSONObject 再 转换成Map
        /**
         * 这段代码是将一个JSON格式的表单数据转换为Java中的Map对象。我来逐步解释：
         * JSONObject jsonObject = JSON.parseObject(process.getFormValues());
         * 调用某个process对象的getFormValues()方法获取表单值
         * 使用JSON.parseObject()方法将这些值解析为一个JSONObject对象
         * JSONObject formData = jsonObject.getJSONObject("formData");
         * 从解析出的jsonObject中获取名为"formData"的JSON对象
         * 这表示原始JSON数据中有一个formData字段，它本身也是一个JSON对象
         * Map<String, Object> map = new HashMap<>();
         * 创建一个新的HashMap实例，用于存储转换后的数据
         * 循环转换部分：
         * java
         * for (Map.Entry<String, Object> entry : formData.entrySet()) {
         *     map.put(entry.getKey(), entry.getValue());
         * }
         * 遍历formData JSON对象中的所有键值对(entry)
         * 将每个键值对的key和value放入新创建的map中
         * 这样就将JSONObject转换为了标准的Java Map
         * 这段代码的最终效果是将嵌套在JSON数据中的formData对象提取出来，转换为更易于Java程序处理的Map结构。这种转换在处理前端提交的表单数据时很常见，特别是当表单数据以JSON格式传输时。
         */
        JSONObject jsonObject = JSON.parseObject(process.getFormValues());
        JSONObject formData = jsonObject.getJSONObject("formData");
        Map<String, Object> map = new HashMap<>();
        //循环转换
        for (Map.Entry<String, Object> entry : formData.entrySet()) {
            map.put(entry.getKey(), entry.getValue());
        }
        //封装流程参数
        variables.put("data", map);
        //启动流程实例
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processTemplate.getProcessDefinitionKey(), businessKey, variables);
        //业务表关联当前流程实例id
        String processInstanceId = processInstance.getId();
        process.setProcessInstanceId(processInstanceId);

        //计算下一个审批人，可能有多个（并行审批）
        List<Task> taskList = this.getCurrentTaskList(processInstanceId);
        if (!CollectionUtils.isEmpty(taskList)) {
            //创建审批人集合
            List<String> assigneeList = new ArrayList<>();
            for(Task task : taskList) {
                //根据审批人的用户名查询审批人信息
                System.out.println("审批人：" + task.getAssignee());
                SysUser user = sysUserService.getByUsername(task.getAssignee());
                //将审批人的真实姓名存入
                assigneeList.add(user.getName());

                //TODO推送消息给下一个审批人，后续完善
                messageService.pushPendingMessage(process.getId(), sysUser.getId(), task.getId());
                //记录操作行为
                oaProcessRecordService.record(process.getId(), 1, "发起申请");
            }
            process.setDescription("等待" + StringUtils.join(assigneeList.toArray(), ",") + "审批");
        }
        oaProcessMapper.updateById(process);
    }

    /**
     * 根据流程实例id查询当前任务节点
     * @param processInstanceId
     * @return
     */
    private List<Task> getCurrentTaskList(String processInstanceId) {
        List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
        return tasks;
    }

    /**
     * 根据当前用户查询他的待处理列表
     * @param pageParam
     * @return
     */
    @Override
    public IPage<ProcessVo> findPending(Page<Process> pageParam) {
        // 封装查询条件，根据当前登录的用户名称
        TaskQuery query = taskService.createTaskQuery().taskAssignee(LoginUserInfoHelper.getUsername()).orderByTaskCreateTime().desc();
        //调用方法分页条件查询，返回list集合，待办任务集合
        List<Task> list = query.listPage((int) ((pageParam.getCurrent() - 1) * pageParam.getSize()), (int) pageParam.getSize());
        //总记录数
        long totalCount = query.count();
        // 封装返回list集合数据 到List<ProcessVo>里面
        List<ProcessVo> processList = new ArrayList<>();
        // 根据流程的业务ID查询实体并关联
        for (Task item : list) {
            // 流程实例id
            String processInstanceId = item.getProcessInstanceId();
            // 查询流程实例
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            if (processInstance == null) {
                continue;
            }
            // 业务key
            String businessKey = processInstance.getBusinessKey();
            if (businessKey == null) {
                continue;
            }
            //根据业务key查询流程信息
            Process process = this.getById(Long.parseLong(businessKey));
            ProcessVo processVo = new ProcessVo();
            BeanUtils.copyProperties(process, processVo);
            processVo.setTaskId(item.getId());
            processList.add(processVo);
        }
        // 封装page对象
        IPage<ProcessVo> page = new Page<ProcessVo>(pageParam.getCurrent(), pageParam.getSize(), totalCount);
        page.setRecords(processList);
        return page;
    }

    @Override
    public Map<String, Object> show(Long id) {
        // 根据id查询流程信息
        Process process = this.getById(id);
        // 查询processId当前流程记录
        List<ProcessRecord> processRecordList = oaProcessRecordService.list(new LambdaQueryWrapper<ProcessRecord>().eq(ProcessRecord::getProcessId, id));
        // 根据ProcessTemplateId查询流程模板
        ProcessTemplate processTemplate = oaProcessTemplateService.getById(process.getProcessTemplateId());
        // 封装数据
        Map<String, Object> map = new HashMap<>();
        map.put("process", process);
        map.put("processRecordList", processRecordList);
        map.put("processTemplate", processTemplate);
        //计算当前用户是否可以审批，能够查看详情的用户不是都能审批，审批后也不能重复审批
        boolean isApprove = false;
        //可能存在并行任务，一个任务同时多人审批
        List<Task> taskList = this.getCurrentTaskList(process.getProcessInstanceId());
        if (!CollectionUtils.isEmpty(taskList)) {
            //遍历
            for(Task task : taskList) {
                if(task.getAssignee().equals(LoginUserInfoHelper.getUsername())) {
                    isApprove = true;
                }
            }
        }
        map.put("isApprove", isApprove);
        return map;
    }

    /**
     * 获取当前用户可处理的任务列表
     * @param approvalVo
     */
    public void approve(ApprovalVo approvalVo) {
        //1 从approvalVo获取任务id，根据任务id获取指定任务ID(approvalVo.getTaskId())的所有流程变量，
        // 并将这些变量以键值对的形式存储在variables1这个Map集合中。
        Map<String, Object> variables1 = taskService.getVariables(approvalVo.getTaskId());
        for (Map.Entry<String, Object> entry : variables1.entrySet()) {
            System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
        }
        //12 判断审批状态值
        String taskId = approvalVo.getTaskId();
        if (approvalVo.getStatus() == 1) {
            //已通过
            Map<String, Object> variables = new HashMap<String, Object>();
            taskService.complete(taskId, variables);
        } else {
            //驳回
            this.endTask(taskId);
        }
        // 记录审批相关过程信息 oa process record
        String description = approvalVo.getStatus().intValue() == 1 ? "已通过" : "驳回";
        oaProcessRecordService.record(approvalVo.getProcessId(), approvalVo.getStatus(), description);

        //计算下一个审批人
        Process process = this.getById(approvalVo.getProcessId());
        //根据ProcessInstanceId（流程实例id）查询实例下的所有任务（可能有并行任务）
        List<Task> taskList = this.getCurrentTaskList(process.getProcessInstanceId());
        //判断下面是否有审批人
        if (!CollectionUtils.isEmpty(taskList)) {
            //遍历
            //存储下一任务审批人的集合
            List<String> assigneeList = new ArrayList<>();
            for(Task task : taskList) {
                SysUser sysUser = sysUserService.getByUsername(task.getAssignee());
                assigneeList.add(sysUser.getName());

                //TODO推送消息给下一个审批人
                messageService.pushPendingMessage(process.getId(), sysUser.getId(), task.getId());
            }
            process.setDescription("等待" + StringUtils.join(assigneeList.toArray(), ",") + "审批");
            process.setStatus(1);
        } else {
            if(approvalVo.getStatus().intValue() == 1) {
                process.setDescription("审批完成（同意）");
                process.setStatus(2);
            } else {
                process.setDescription("审批完成（拒绝）");
                process.setStatus(-1);
            }
        }
        //推送消息给申请人
        this.updateById(process);
    }

    /**
     * 取消流程
     * @param taskId
     */
    private void endTask(String taskId) {
        //  当前任务
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();

        // 获取流程模板
        BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
        // 获取结束流向节点
        List endEventList = bpmnModel.getMainProcess().findFlowElementsOfType(EndEvent.class);
        // 并行任务可能为null
        if(CollectionUtils.isEmpty(endEventList)) {
            return;
        }
        FlowNode endFlowNode = (FlowNode) endEventList.get(0);
        //当前流向节点
        FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(task.getTaskDefinitionKey());

        //  临时保存当前活动的原始方向
        List originalSequenceFlowList = new ArrayList<>();
        originalSequenceFlowList.addAll(currentFlowNode.getOutgoingFlows());
        //  清理活动方向
        currentFlowNode.getOutgoingFlows().clear();

        //  建立新方向
            SequenceFlow newSequenceFlow = new SequenceFlow();
            //  设置新方向id
            newSequenceFlow.setId("newSequenceFlowId");
            //  设置新方向的源节点
            newSequenceFlow.setSourceFlowElement(currentFlowNode);
            //  设置新方向的目标节点
            newSequenceFlow.setTargetFlowElement(endFlowNode);
            //  新方向的属性
            List newSequenceFlowList = new ArrayList<>();//为什么是list集合？ 因为节点可以有多个方向，集合中存放多个，在这里没有多个方向，所以集合中只有一个
            newSequenceFlowList.add(newSequenceFlow);

        //  当前节点指向新的方向(newSequenceFlowList)
        currentFlowNode.setOutgoingFlows(newSequenceFlowList);

        //  完成当前任务
        taskService.complete(task.getId());
    }


    /**
     * 已处理
     * @param pageParam
     * @return
     */
    @Override
    public IPage<ProcessVo> findProcessed(Page<Process> pageParam) {
        // 根据当前用户的ID查询其已处理的任务
        HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery()
                                                        .taskAssignee(LoginUserInfoHelper.getUsername())
                                                        .finished()
                                                        .orderByTaskCreateTime()
                                                        .desc();
        // 开始位置
        int begin= (int) ((pageParam.getCurrent() - 1) * pageParam.getSize());
        // 结束位置
        int size=(int) pageParam.getSize();
        // 设置分页参数
        List<HistoricTaskInstance> list = query.listPage(begin,size);
        // 总记录数
        long totalCount = query.count();

        List<ProcessVo> processList = new ArrayList<>();
        for (HistoricTaskInstance item : list) {
            // 获取流程实例id
            String processInstanceId = item.getProcessInstanceId();
            // 根据流程实例id查询流程信息
            Process process = this.getOne(new LambdaQueryWrapper<Process>().eq(Process::getProcessInstanceId, processInstanceId));
            ProcessVo processVo = new ProcessVo();
            BeanUtils.copyProperties(process, processVo);
            // 设置任务id为0
            processVo.setTaskId("0");
            processList.add(processVo);
        }
        IPage<ProcessVo> page = new Page<>(pageParam.getCurrent(), pageParam.getSize(), totalCount);
        page.setRecords(processList);
        return page;
    }

    /**
     * 待处理
     * @param pageParam
     * @return
     */
    @Override
    public IPage<ProcessVo> findStarted(Page<ProcessVo> pageParam) {
        ProcessQueryVo processQueryVo = new ProcessQueryVo();
        processQueryVo.setUserId(LoginUserInfoHelper.getUserId());
        IPage<ProcessVo> page = oaProcessMapper.selectPage(pageParam, processQueryVo);
        for (ProcessVo item : page.getRecords()) {
            item.setTaskId("0");
        }
        return page;
    }

}